package com.lyt.mall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.lyt.mall.product.service.CategoryBrandRelationService;
import com.lyt.mall.product.vo.Catalog2VO;

import org.apache.skywalking.apm.toolkit.trace.Trace;
import org.jetbrains.annotations.Nullable;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lyt.common.utils.PageUtils;
import com.lyt.common.utils.Query;

import com.lyt.mall.product.dao.CategoryDao;
import com.lyt.mall.product.entity.CategoryEntity;
import com.lyt.mall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * 查询所有的类别数据，然后将数据封装为树形结构，便于前端使用
     * @param params
     * @return
     */
    @Override
    public List<CategoryEntity> queryPageWithTree(Map<String, Object> params) {
        //1.查询所有的商品分类信息
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);
        //2.将商品分类信息拆解为树形结构[父子关系]
        //2.1遍历出所有的大类
        List<CategoryEntity> list = categoryEntities.stream().filter(categoryEntity -> categoryEntity.getParentCid() == 0)
                .map(categoryEntity -> {
                    //2.2根据大类找到对应的所有的小类 递归方式实现
                    categoryEntity.setChildren(getCategoryChildren(categoryEntity,categoryEntities));
                    return categoryEntity;
                }).sorted((entity1, entity2) -> {
                    return (entity1.getSort() == null ? 0 : entity1.getSort()) - (entity2.getSort() == null ? 0 : entity2.getSort());
                }).collect(Collectors.toList());

        return list;
    }

    /**
     * 查找该大类下的所有的小类 递归查找
     * @param categoryEntity 某个大类
     * @param categoryEntities 所有的类别数据
     * @return
     */
    private List<CategoryEntity> getCategoryChildren(CategoryEntity categoryEntity, List<CategoryEntity> categoryEntities) {
        List<CategoryEntity> collect = categoryEntities.stream().filter(entity -> {
            //根据大类找到其直属的小类
            //PS:Long数据比较时，不在-128~127之间的数据是new Long()对象比较，不能使用==进行比较
            return entity.getParentCid().equals(categoryEntity.getCatId());
        }).map(entity -> {
            //根据这个小类递归找到对应的小小类
            entity.setChildren(getCategoryChildren(entity, categoryEntities));
            return entity;
        }).sorted((entity1, entity2) -> {
            return (entity1.getSort() == null ? 0 : entity1.getSort()) - (entity2.getSort() == null ? 0 : entity2.getSort());
        }).collect(Collectors.toList());

        return collect;
    }

    /**
     * 逻辑批量删除操作
     * @param ids
     */
    @Override
    public void removeCategoryByIds(List<Long> ids) {
        //TODO 1.检查类别数据是否在其他业务中使用
        //2.批量逻辑删除操作
        baseMapper.deleteBatchIds(ids);
    }

    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        List<Long> parentPath = findParentPath(catelogId, paths);
        Collections.reverse(parentPath);
        return parentPath.toArray(new Long[parentPath.size()]);
    }

    /**
     * @CacheEvict 在更新数据的时候同步删除缓存中的数据
     * @CacheEvict(value = "catagory",allEntries = true) 表示删除catagory分区下所有的缓存数据
     * @param entity
     */
    /*@Caching(evict = {
            @CacheEvict(value = "catagory",key = "'getLevelCategory'"),
            @CacheEvict(value = "catagory",key = "'getCatelog2JSON'")
    })*/
    @CacheEvict(value = "catagory",allEntries = true)
    @Transactional
    @Override
    public void updateDetail(CategoryEntity entity) {
        //更新类别名称
        this.updateById(entity);
        if(!StringUtils.isEmpty(entity.getName())){
            // 同步更新级联的数据
            categoryBrandRelationService.updateCatelogName(entity.getCatId(),entity.getName());
            //TODO 同步更新其他的冗余数据
        }
    }

    /**
     * 查询所有的商品大类（一级分类）
     * 在注解中我们可以指定对应的缓存的名称，起到一个分区的作用，一般按照业务来区分
     * @Cacheable({"catagory","product"}) 代表当前的方法的返回结果是需要缓存的
     *                                    调用该方法的时候，如果缓存中有数据，那么该方法就不会执行
     *                                    如果缓存中没有数据，那么就执行该方法并且把查询的结果缓存起来
     * 缓存处理：
     *      1.存储在Redis中的缓存数据的Key是默认生成的：缓存名称：SimpleKey[]
     *      2.默认缓存的数据的过期时间是-1永久
     *      3.缓存的数据，默认使用的是JDK的序列化机制
     * 改进：
     *      1.生成的缓存数据需要指定自定义的key   key = "#root.method.name"
     *      2.指定缓存数据的存活时间  spring.cache.redis.time-to-live
     *      3.把缓存的数据保存为JSON数据
     * @return
     */
    @Trace
    @Cacheable(value = "catagory",key = "#root.method.name") //触发缓存的保存操作
    @Override
    public List<CategoryEntity> getLevelCategory() {
        return baseMapper.queryLevelCategory();
    }

    /**
     * 根据父编号获取对应的子菜单信息
     * @param list
     * @param parentCid
     * @return
     */
    private List<CategoryEntity> queryByParentCid(List<CategoryEntity> list,Long parentCid){
        return list.stream().filter(item -> item.getParentCid().equals(parentCid)).collect(Collectors.toList());
    }

    //本地缓存
    private Map<String,Map<String,List<Catalog2VO>>> cache = new HashMap<>();

    /**
     * 查询所有分类数据，并且完成一级二级三级的关联
     * @return
     */
    @Cacheable(value = "catagory",key = "#root.methodName")
    @Override
    public Map<String,List<Catalog2VO>> getCatelog2JSON(){
        //获取所有的分类数据
        List<CategoryEntity>  list = baseMapper.selectList(new QueryWrapper<CategoryEntity>());
        //获取所有的一级分类的数据
        List<CategoryEntity> levelCategory = this.queryByParentCid(list,0L);
        //把一级分类的数据转换为Map容器，key就是一级分类的编号，value就是一级分类对应的二级分类的数据
        Map<String,List<Catalog2VO>> map = levelCategory.stream().collect(Collectors.toMap(key -> key.getCatId().toString(), value -> {
            //根据一级分类的编号，查询出对应的二级分类的数据
            List<CategoryEntity> l2Catalogs = this.queryByParentCid(list,value.getCatId());
            List<Catalog2VO> Catalog2VOs = null;
            if (l2Catalogs != null){
                Catalog2VOs = l2Catalogs.stream().map((l2) -> {
                    //需要把查询出来的二级分类的数据填充到对应的Catelog2VO中
                    Catalog2VO catalog2VO = new Catalog2VO(l2.getParentCid().toString(), null, l2.getCatId().toString(), l2.getName());
                    //根据二级分类的数据找到对应的三级分类的信息
                    List<CategoryEntity> l3Catelogs = this.queryByParentCid(list, l2.getCatId());
                    if (l3Catelogs != null){
                        //获取到的二级分类对应的三级分类的数据
                        List<Catalog2VO.Catalog3VO> catalog3VOS = l3Catelogs.stream().map((l3)->{
                            Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO(l3.getParentCid().toString(),l3.getCatId().toString(),l3.getName().toString());
                            return catalog3VO;
                        }).collect(Collectors.toList());
                        //三级分类关联二级分类
                        catalog2VO.setCatalog3List(catalog3VOS);
                    }
                    return catalog2VO;
                }).collect(Collectors.toList());
            }
            return Catalog2VOs;
        }));
        return map;
    }

    /**
     * 查询所有的二级和三级分类的数据
     * 并封装为Map<String, Catalog2VO>对象
     * @return
     */
    //@Override
    public Map<String,List<Catalog2VO>> getCatelog2JSONRedis(){
        String key = "catalogJSON";
        //从Redis中获取分类的信息
        String catalogJSON = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isEmpty(catalogJSON)){
            System.out.println("缓存没有命中......");
            //缓存中没有数据，需要从数据库中查询
            Map<String,List<Catalog2VO>> catelog2JSONForDB = getCatelog2JSONDBWithRedisLock();

            return catelog2JSONForDB;
        }
        System.out.println("缓存命中了......");
        //表示缓存命中了数据，那么从缓存中获取信息，然后返回
        Map<String, List<Catalog2VO>> stringListMap = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>(){});
        return stringListMap;
    }

    public Map<String,List<Catalog2VO>> getCatelog2JSONDBWithRedisson() {
        String keys = "catalogJSON";
        //获取分布式锁对象
        //PS:加锁的时候要注意锁的名称
        RLock lock = redissonClient.getLock("catelog2JSON-lock");
        Map<String, List<Catalog2VO>> data = null;
        try{
            lock.lock();
            //加锁成功
            data = getDataForDB(keys);
        }finally {
            lock.unlock();
        }
        return data;
    }

    public Map<String,List<Catalog2VO>> getCatelog2JSONDBWithRedisLock() {
        String keys = "catalogJSON";
        //加锁,在执行插入操作的同时设置过期时间
        String uuid = UUID.randomUUID().toString();
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);
        if (Boolean.TRUE.equals(lock)){
            System.out.println("获取分布式锁成功......");
            Map<String, List<Catalog2VO>> data = null;
            try{
                //加锁成功
                data = getDataForDB(keys);
            }finally {
                String srcipts = "if redis.call('get',KEYS[1]) == ARGV[1]  then return redis.call('del',KEYS[1]) else  return 0 end ";
                // 通过Redis的lua脚本实现 查询和删除操作的原子性
                stringRedisTemplate.execute(new DefaultRedisScript<>(srcipts, Long.class)
                        ,Arrays.asList("lock"),uuid);
            }
            return data;
        }else {
            //加锁失败
            //休眠+重试
            try{
                Thread.sleep(200);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("获取分布式锁失败......");
            return getCatelog2JSONDBWithRedisLock();
        }
    }

    /**
     * 从数据库中查询操作
     * @param keys
     * @return
     */
    @Nullable
    private Map<String, List<Catalog2VO>> getDataForDB(String keys) {
        //从Redis中获取分类的信息
        String catalogJSON = stringRedisTemplate.opsForValue().get(keys);
        if (!StringUtils.isEmpty(catalogJSON)){
            //说明缓存命中
            //表示缓存命中了数据，那么从缓存中获取信息，然后返回
            Map<String, List<Catalog2VO>> stringListMap = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>(){});
            return stringListMap;
        }
        System.out.println("-------------->查询数据库操作");

        //获取所有的分类数据
        List<CategoryEntity>  list = baseMapper.selectList(new QueryWrapper<CategoryEntity>());
        //获取所有的一级分类的数据
        List<CategoryEntity> levelCategory = this.queryByParentCid(list,0L);
        //把一级分类的数据转换为Map容器，key就是一级分类的编号，value就是一级分类对应的二级分类的数据
        Map<String,List<Catalog2VO>> map = levelCategory.stream().collect(Collectors.toMap(key -> key.getCatId().toString(), value -> {
            //根据一级分类的编号，查询出对应的二级分类的数据
            List<CategoryEntity> l2Catalogs = this.queryByParentCid(list,value.getCatId());
            List<Catalog2VO> Catalog2VOs = null;
            if (l2Catalogs != null){
                Catalog2VOs = l2Catalogs.stream().map((l2) -> {
                    //需要把查询出来的二级分类的数据填充到对应的Catelog2VO中
                    Catalog2VO catalog2VO = new Catalog2VO(l2.getParentCid().toString(), null, l2.getCatId().toString(), l2.getName());
                    //根据二级分类的数据找到对应的三级分类的信息
                    List<CategoryEntity> l3Catelogs = this.queryByParentCid(list, l2.getCatId());
                    if (l3Catelogs != null){
                        //获取到的二级分类对应的三级分类的数据
                        List<Catalog2VO.Catalog3VO> catalog3VOS = l3Catelogs.stream().map((l3)->{
                            Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO(l3.getParentCid().toString(),l3.getCatId().toString(),l3.getName().toString());
                            return catalog3VO;
                        }).collect(Collectors.toList());
                        //三级分类关联二级分类
                        catalog2VO.setCatalog3List(catalog3VOS);
                    }
                    return catalog2VO;
                }).collect(Collectors.toList());
            }
            return Catalog2VOs;
        }));
        //从数据库中获取到了对应的信息，然后在缓存中也存储一份信息
        //cache.put("getCatelog2JSON",map);
        if (map == null){
            //防止缓存穿透
            stringRedisTemplate.opsForValue().set(keys,"1",5, TimeUnit.SECONDS);
        }else{
            //从数据库中查询到的数据，需要给缓存中也存储一份
            //防止缓存雪崩
            String json = JSON.toJSONString(map);
            stringRedisTemplate.opsForValue().set("catalogJSON",json,10,TimeUnit.MINUTES);
        }
        return map;
    }

    /**
     * 从数据库查询的结果
     * 并封装为Map<String, Catalog2VO>对象
     * @return
     */
    public Map<String,List<Catalog2VO>> getCatelog2JSONForDB() {
        String keys = "catalogJSON";
        //在SpringBoot中，默认的情况下是单例
        synchronized (this){
            //从Redis中获取分类的信息
            String catalogJSON = stringRedisTemplate.opsForValue().get(keys);
            if (!StringUtils.isEmpty(catalogJSON)){
                //说明缓存命中
                //表示缓存命中了数据，那么从缓存中获取信息，然后返回
                Map<String, List<Catalog2VO>> stringListMap = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>(){});
                return stringListMap;
            }
            System.out.println("-------------->查询数据库操作");

            //获取所有的分类数据
            List<CategoryEntity>  list = baseMapper.selectList(new QueryWrapper<CategoryEntity>());
            //获取所有的一级分类的数据
            List<CategoryEntity> levelCategory = this.queryByParentCid(list,0L);
            //把一级分类的数据转换为Map容器，key就是一级分类的编号，value就是一级分类对应的二级分类的数据
            Map<String,List<Catalog2VO>> map = levelCategory.stream().collect(Collectors.toMap(key -> key.getCatId().toString(), value -> {
                //根据一级分类的编号，查询出对应的二级分类的数据
                List<CategoryEntity> l2Catalogs = this.queryByParentCid(list,value.getCatId());
                List<Catalog2VO> Catalog2VOs = null;
                if (l2Catalogs != null){
                    Catalog2VOs = l2Catalogs.stream().map((l2) -> {
                        //需要把查询出来的二级分类的数据填充到对应的Catelog2VO中
                        Catalog2VO catalog2VO = new Catalog2VO(l2.getParentCid().toString(), null, l2.getCatId().toString(), l2.getName());
                        //根据二级分类的数据找到对应的三级分类的信息
                        List<CategoryEntity> l3Catelogs = this.queryByParentCid(list, l2.getCatId());
                        if (l3Catelogs != null){
                            //获取到的二级分类对应的三级分类的数据
                            List<Catalog2VO.Catalog3VO> catalog3VOS = l3Catelogs.stream().map((l3)->{
                                Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO(l3.getParentCid().toString(),l3.getCatId().toString(),l3.getName().toString());
                                return catalog3VO;
                            }).collect(Collectors.toList());
                            //三级分类关联二级分类
                            catalog2VO.setCatalog3List(catalog3VOS);
                        }
                        return catalog2VO;
                    }).collect(Collectors.toList());
                }
                return Catalog2VOs;
            }));
            //从数据库中获取到了对应的信息，然后在缓存中也存储一份信息
            //cache.put("getCatelog2JSON",map);
            if (map == null){
                //防止缓存穿透
                stringRedisTemplate.opsForValue().set(keys,"1",5, TimeUnit.SECONDS);
            }else{
                //从数据库中查询到的数据，需要给缓存中也存储一份
                //防止缓存雪崩
                String json = JSON.toJSONString(map);
                stringRedisTemplate.opsForValue().set("catalogJSON",json,10,TimeUnit.MINUTES);
            }
            return map;
        }
    }

    /**
     * @param catelogId
     * @param paths
     * @return
     */
    private List<Long> findParentPath(Long catelogId,List<Long> paths){
        paths.add(catelogId);
        CategoryEntity entity = this.getById(catelogId);
        if(entity.getParentCid() != 0){
            findParentPath(entity.getParentCid(),paths);
        }
        return paths;
    }
}